
    <!DOCTYPE html>
    <html lang="zh-CN">
    <head>
      <meta charset="UTF-8">
      <meta name="viewport" content="width=device-width, initial-scale=1.0">
      <title>5种神器解决SpringBoot公共字段填充 - 学习卡片</title>
      <style>
        body { font-family: sans-serif; background-color: #f0f8ff; color: #333; display: flex; flex-direction: column; align-items: center; padding: 50px 20px; }
        .header h1 { font-size: 32px; }
        .grid-container { display: grid; grid-template-columns: repeat(3, 1fr); gap: 28px; width: 100%; max-width: 1200px; }
        .card-container { perspective: 1200px; cursor: pointer; height: 250px; }
        .card { width: 100%; height: 100%; position: relative; transform-style: preserve-3d; transition: transform 0.7s; border-radius: 16px; box-shadow: 0 4px 16px rgba(0,0,0,0.08); }
        .card-container.flipped .card { transform: rotateY(180deg); }
        .card-face { position: absolute; width: 100%; height: 100%; backface-visibility: hidden; display: flex; flex-direction: column; box-sizing: border-box; border-radius: 16px; background-color: #fff; padding: 24px; }
        .card-back { background-color: #f0fff4; transform: rotateY(180deg); justify-content: space-between; }
        .card-category { font-size: 14px; color: #0052d9; margin-bottom: 8px; font-weight: 500; }
        .card-question { font-size: 20px; font-weight: 500; flex-grow: 1; display: flex; align-items: center; justify-content: center; text-align: center; }
        .card-answer-wrapper { flex-grow: 1; overflow-y: auto; }
        .card-answer { font-size: 15px; line-height: 1.7; }
        .card-footer { font-size: 13px; color: #8a919f; border-top: 1px solid #f0f0f0; padding-top: 16px; margin-top: 16px; }
        .card-source { font-size: 13px; color: #8a919f; border-top: 1px solid #f0f0f0; padding-top: 12px; margin-top: 12px; }
      </style>
    </head>
    <body>
      <div class="header">
        <h1>5种神器解决SpringBoot公共字段填充 - 学习卡片</h1>
      </div>
      <div class="grid-container">
        
    <div class="card-container" onclick="this.classList.toggle('flipped');">
      <div class="card">
        <div class="card-face card-front">
          <div class="card-category">理论</div>
          <div class="card-question">传统的手动在Service层填充公共字段（如创建时间、创建人）方法存在哪些主要痛点？</div>
          <div class="card-footer">点击卡片查看答案</div>
        </div>
        <div class="card-face card-back">
          <div class="card-category">理论</div>
          <div class="card-answer-wrapper">
            <div class="card-answer">主要有三大痛点：1. 重复代码：随着实体类增多，需要在大量Service中编写相似代码，违反DRY原则，导致维护噩梦。2. 容易遗漏：开发人员可能忘记设置这些字段，导致数据不一致。3. 维护成本高：当需要新增公共字段（如租户ID）时，必须修改所有相关的Service类。</div>
          </div>
          <div class="card-source">来源: 一、传统手动填充法及其痛点</div>
        </div>
      </div>
    </div>

    <div class="card-container" onclick="this.classList.toggle('flipped');">
      <div class="card">
        <div class="card-face card-front">
          <div class="card-category">技术</div>
          <div class="card-question">在MyBatis-Plus中实现公共字段自动填充需要哪两个核心步骤？</div>
          <div class="card-footer">点击卡片查看答案</div>
        </div>
        <div class="card-face card-back">
          <div class="card-category">技术</div>
          <div class="card-answer-wrapper">
            <div class="card-answer">核心步骤有两个：1. 创建一个实现 `MetaObjectHandler` 接口的组件，并在 `insertFill` 和 `updateFill` 方法中定义字段填充逻辑。2. 在实体类的相应字段上使用 `@TableField(fill = ...)` 注解，并指定填充策略（如 `FieldFill.INSERT` 或 `FieldFill.UPDATE`）。</div>
          </div>
          <div class="card-source">来源: 二、MyBatis-Plus自动填充神器</div>
        </div>
      </div>
    </div>

    <div class="card-container" onclick="this.classList.toggle('flipped');">
      <div class="card">
        <div class="card-face card-front">
          <div class="card-category">机制</div>
          <div class="card-question">Hibernate拦截器方案是如何在实体保存和更新时自动填充字段的？</div>
          <div class="card-footer">点击卡片查看答案</div>
        </div>
        <div class="card-face card-back">
          <div class="card-category">机制</div>
          <div class="card-answer-wrapper">
            <div class="card-answer">通过创建一个继承自 `EmptyInterceptor` 的自定义拦截器，并重写 `onSave` 和 `onFlushDirty` 方法。`onSave` 方法会在实体保存前被调用，`onFlushDirty` 方法会在实体更新时被调用。在这两个方法中，可以遍历实体的属性，找到需要填充的公共字段并为其赋值。</div>
          </div>
          <div class="card-source">来源: 三、Hibernate Interceptor拦截器方案</div>
        </div>
      </div>
    </div>

    <div class="card-container" onclick="this.classList.toggle('flipped');">
      <div class="card">
        <div class="card-face card-front">
          <div class="card-category">模式</div>
          <div class="card-question">当使用AOP切面方案时，文档提到了哪两种策略来区分是“保存”操作还是“更新”操作？</div>
          <div class="card-footer">点击卡片查看答案</div>
        </div>
        <div class="card-face card-back">
          <div class="card-category">模式</div>
          <div class="card-answer-wrapper">
            <div class="card-answer">文档提到了两种策略：1. 基于方法命名约定：通过切点表达式捕获所有以特定前缀（如 `save` 或 `update`）开头的方法，并据此判断操作类型。2. 使用自定义注解：定义一个注解（如 `@FillAuditFields`）来标记方法，并通过注解的属性（如 `OperationType.INSERT`）来明确指定操作类型，增加灵活性。</div>
          </div>
          <div class="card-source">来源: 四、全局统一的AOP切面方案</div>
        </div>
      </div>
    </div>

    <div class="card-container" onclick="this.classList.toggle('flipped');">
      <div class="card">
        <div class="card-face card-front">
          <div class="card-category">技术</div>
          <div class="card-question">在Spring Data JPA中，要启用对创建/修改人（`@CreatedBy`, `@LastModifiedBy`）的自动填充，需要提供哪个关键的Bean？</div>
          <div class="card-footer">点击卡片查看答案</div>
        </div>
        <div class="card-face card-back">
          <div class="card-category">技术</div>
          <div class="card-answer-wrapper">
            <div class="card-answer">需要提供一个实现了 `AuditorAware<T>` 接口的Spring Bean。这个Bean的 `getCurrentAuditor()` 方法负责从安全上下文（如 `SecurityContextHolder`）中获取当前用户的标识，JPA的 `AuditingEntityListener` 会调用它来填充用户相关字段。</div>
          </div>
          <div class="card-source">来源: 五、JPA实体监听器方案</div>
        </div>
      </div>
    </div>

    <div class="card-container" onclick="this.classList.toggle('flipped');">
      <div class="card">
        <div class="card-face card-front">
          <div class="card-category">特性</div>
          <div class="card-question">根据文档的性能基准测试，五种公共字段填充方案中，哪一种性能最好，哪一种相对最差？</div>
          <div class="card-footer">点击卡片查看答案</div>
        </div>
        <div class="card-face card-back">
          <div class="card-category">特性</div>
          <div class="card-answer-wrapper">
            <div class="card-answer">性能最好的是“手动填充”，因为它没有任何额外的反射或代理开销。性能相对最差的是“AOP切面方案”，因为它引入的代理和反射机制会产生较大开销，在测试中比手动填充方案慢了大约20%。</div>
          </div>
          <div class="card-source">来源: 六、五种方案的综合对比与选型指南</div>
        </div>
      </div>
    </div>

    <div class="card-container" onclick="this.classList.toggle('flipped');">
      <div class="card">
        <div class="card-face card-front">
          <div class="card-category">理论</div>
          <div class="card-question">为什么说“手动填充”方案虽然代码侵入性最高，但在某些场景下（如高安全性要求）仍有其价值？</div>
          <div class="card-footer">点击卡片查看答案</div>
        </div>
        <div class="card-face card-back">
          <div class="card-category">理论</div>
          <div class="card-answer-wrapper">
            <div class="card-answer">因为手动填充方案没有任何“魔法”，每一行代码的行为都清晰可见，调试容易。这种直观和可控性为应用提供了最高的可审计性，在对安全性要求极高的场景下，能够确保所有赋值操作都是明确和可追溯的。</div>
          </div>
          <div class="card-source">来源: 六、五种方案的综合对比与选型指南</div>
        </div>
      </div>
    </div>

    <div class="card-container" onclick="this.classList.toggle('flipped');">
      <div class="card">
        <div class="card-face card-front">
          <div class="card-category">模式</div>
          <div class="card-question">对于使用Spring Data JPA的项目，为什么JPA实体监听器被认为是一种“极其优雅”的解决方案？</div>
          <div class="card-footer">点击卡片查看答案</div>
        </div>
        <div class="card-face card-back">
          <div class="card-category">模式</div>
          <div class="card-answer-wrapper">
            <div class="card-answer">因为它与JPA框架深度集成，代码简洁，配置简单。开发者只需通过 `@EnableJpaAuditing` 开启功能，并在实体字段上添加 `@CreatedDate`、`@CreatedBy` 等注解即可，对业务代码的侵入性极低，实现了声明式的字段自动填充。</div>
          </div>
          <div class="card-source">来源: 五、JPA实体监听器方案</div>
        </div>
      </div>
    </div>

    <div class="card-container" onclick="this.classList.toggle('flipped');">
      <div class="card">
        <div class="card-face card-front">
          <div class="card-category">机制</div>
          <div class="card-question">文档指出AOP切面方案存在哪些局限性？</div>
          <div class="card-footer">点击卡片查看答案</div>
        </div>
        <div class="card-face card-back">
          <div class="card-category">机制</div>
          <div class="card-answer-wrapper">
            <div class="card-answer">AOP方案的局限性在于：它无法感知批量操作中的每一个独立实体，以及在处理复杂的对象图时不够优雅。此外，它的性能相对较差，因为代理和反射会带来额外的开销。</div>
          </div>
          <div class="card-source">来源: 四、全局统一的AOP切面方案</div>
        </div>
      </div>
    </div>

      </div>
    </body>
    </html>
